﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using MacomberMapSystem.Common.Serialization;
using System.Xml;
using MacomberMapSystem.Common.Integration;
using MacomberMapSystem.Common.Database;
using MacomberMapSystem.Common.Internals;
using System.Collections;

namespace MacomberMapSystem.Common.CIM
{
    /// <summary>
    /// This class holds information on a contingency grouping
    /// </summary>
    public class CIM_Contingency_Grouping
    {
        #region Variable declarations
        /// <summary>The definition in CIM on which we're based.</summary>
        public CIM_Element BaseElement;

        /// <summary>Our collection of special elements</summary>
        public CIM_ContSpecialElements[] SpecialElements;

        /// <summary>The subgroups associated with an aggregated definition</summary>
        public CIM_Contingency_Grouping[] Subgroups;
        #endregion

        #region Initialization
        /// <summary>
        /// Initialize a new contingnecy grouping list
        /// </summary>
        /// <param name="BaseElement"></param>
        /// <param name="CtgGroups">The collection of existing groups</param>
        public CIM_Contingency_Grouping(CIM_Element BaseElement, Dictionary<CIM_Element, CIM_Contingency_Grouping> CtgGroups )
        {
            this.BaseElement = BaseElement;
            List<CIM_ContSpecialElements> SpecialElements = new List<CIM_ContSpecialElements>();
            List<CIM_Contingency_Grouping> Subgroups = new List<CIM_Contingency_Grouping>();
            if (BaseElement.TypeName == "etx:AggrContingencyGroup")
                foreach (CIM_Element CtgGroup in BaseElement.Parse(">etx:ContingencyGroup", BaseElement.CIM))
                    Subgroups.Add(CtgGroups[CtgGroup]);
            else
                foreach (CIM_Element SpecialElement in BaseElement.Parse("<etx:ContingencyGroupMember<etx:ContSpecialElements", BaseElement.CIM))
                    SpecialElements.Add(new CIM_ContSpecialElements(SpecialElement));

            SpecialElements.Sort();
            this.SpecialElements = SpecialElements.ToArray();
            this.Subgroups = Subgroups.ToArray();
        }
        #endregion


        #region Identification
        /// <summary>
        /// Return an easy-to-read identifier.
        /// </summary>
        /// <returns></returns>
        public override string ToString()
        {
            StringBuilder sB = new StringBuilder();
            sB.AppendLine(BaseElement.ToString());
            foreach (CIM_ContSpecialElements SpecialElement in SpecialElements)
                sB.AppendLine("   "+ SpecialElement.ToString());
            return sB.ToString();
        }
        #endregion

        /// <summary>
        /// Test a trace for membership in our group.
        /// </summary>
        /// <param name="Trace"></param>
        /// <param name="ContingencySeeds"></param>
        /// <param name="Groups"></param>
        /// <returns></returns>
        public bool TestMembership(CIM_Trace Trace, Dictionary<CIM_RdfID, CIM_Element> ContingencySeeds, List<CIM_Element> Groups)
        {
            bool Include = false;
            
            //Check for special elements
            foreach (CIM_ContSpecialElements SpecialElement in this.SpecialElements)
                if (SpecialElement.TestMembership(Trace, ContingencySeeds))
                {
                    Include = SpecialElement.IncorporateElements;
                    Console.WriteLine("Membership test for " + SpecialElement.ToString() + " is true.");
                }

            //Check for groups
            if (Subgroups != null)                            
                foreach (CIM_Contingency_Grouping CtgGroup in Subgroups)
                    if (Groups.Contains(CtgGroup.BaseElement))                
                        Include = true;
            return Include;                        
        }

        /// <summary>
        /// This class holds information on our special elements
        /// </summary>
        public class CIM_ContSpecialElements:IComparable
        {
            #region Variable declarations
            /// <summary>The contingency collection enumeration</summary>
            public enumContingencyCollection ContingencyCollections = enumContingencyCollection.Unknown;

            /// <summary>collection of contingecy processing modes</summary>
            public enum enumContingencyCollection
            {
                /// <summary>An unknown type</summary>
                Unknown = 0,
                /// <summary>All equipment</summary>
                AllEquipment = 1,              
                /// <summary>Defined in CIM</summary>
                Lines = 2,
                /// <summary>All lines</summary>
                Units = 3,
                /// <summary>All transformers</summary>
                XFormers = 4,
                /// <summary>Multiple circuits</summary>
                MultipleCircuits=5,
             
            }

            /// <summary>The collection of substations to be tested for</summary>
            public CIM_Element[] Substations;

            /// <summary>The counties to be tested for</summary>
            public MM_Boundary[] Counties;

            /// <summary>The voltages to be tested for</summary>
            public CIM_Element[] Voltages;
         
            /// <summary>The PUNs to be tested for</summary>
            public CIM_Element[] PUNs;

            /// <summary>The definition in CIM on which we're based.</summary>
            public CIM_Element BaseElement;

            /// <summary>Whether to incorporate elements or exclude them.</summary>
            public bool IncorporateElements;
            #endregion

            #region Initialization
            /// <summary>
            /// Initialize a new collection of special elements
            /// </summary>
            /// <param name="BaseElement"></param>
            public CIM_ContSpecialElements(CIM_Element BaseElement)
            {
                this.BaseElement = BaseElement;
                IncorporateElements = Convert.ToBoolean(BaseElement["etx:ContSpecialElements.IncorporateElements"]);
                string ContingencyCollections = BaseElement["etx:ContSpecialElements.ContingencyCollections"];
                try
                {
                    this.ContingencyCollections = (enumContingencyCollection)Enum.Parse(typeof(enumContingencyCollection), ContingencyCollections.Substring(ContingencyCollections.LastIndexOf('.') + 1));
                }
                catch 
                {
                }
                Substations = BaseElement.Parse("cim:Substation", BaseElement.CIM);
                List<MM_Boundary> Coll_Counties = new List<MM_Boundary>();
                foreach (CIM_Element Bound in BaseElement.Parse("etx:GeographicObjects", BaseElement.CIM))
                    Coll_Counties.Add(BaseElement.CIM.Repository.Db.Boundaries[Bound.Name]);
                Counties = Coll_Counties.ToArray();
                Voltages = BaseElement.Parse("cim:BaseVoltage", BaseElement.CIM);
                PUNs = BaseElement.Parse("etx:PrivateAreaNetwork", BaseElement.CIM);
            }
            #endregion

            #region Testing
            /// <summary>
            /// Determine if a trace fits in our group membership
            /// </summary>
            /// <param name="Trace"></param>
            /// <param name="ContingencySeeds"></param>
            /// <returns></returns>            
            public bool TestMembership(CIM_Trace Trace, Dictionary<CIM_RdfID, CIM_Element> ContingencySeeds)
            {
                List<CIM_Element> Voltages, Substations, PUNs;
                List<MM_Boundary> Counties;
                bool FoundAny = false;
                if (ContingencyCollections == enumContingencyCollection.MultipleCircuits && !Trace.Name.StartsWith("D"))
                    return false;
                else if (ContingencyCollections == enumContingencyCollection.Lines)
                    ComputeConnectivity(Trace, ContingencySeeds, "cim:ACLineSegment", out Voltages, out Substations, out Counties, out PUNs, out FoundAny);
                else if (ContingencyCollections == enumContingencyCollection.XFormers)
                    ComputeConnectivity(Trace, ContingencySeeds, "cim:TransformerWinding", out Voltages, out Substations, out Counties, out PUNs, out FoundAny);
                else if (ContingencyCollections == enumContingencyCollection.Units)
                    ComputeConnectivity(Trace, ContingencySeeds, "cim:SynchronousMachine", out Voltages, out Substations, out Counties, out PUNs, out FoundAny);
                else if (ContingencyCollections == enumContingencyCollection.AllEquipment || ContingencyCollections == enumContingencyCollection.MultipleCircuits)
                    ComputeConnectivity(Trace, ContingencySeeds, "", out Voltages, out Substations, out Counties, out PUNs, out FoundAny);
                else
                    //TODO: Add throw error here.
                    return false;


                bool FoundVoltage = false, FoundSubstation = false, FoundCounty = false, FoundPUN = false;

                //Now, check all of our components for matches
                foreach (CIM_Element PUN in this.PUNs)
                    if (PUNs.Contains(PUN))
                        FoundPUN = true;
                foreach (CIM_Element Sub in this.Substations)
                    if (Substations.Contains(Sub))
                        FoundSubstation = true;
                foreach (MM_Boundary County in this.Counties)
                    if (Counties.Contains(County))
                        FoundCounty = true;
                if (ContingencyCollections == enumContingencyCollection.XFormers)
                {
                    foreach (CIM_Element XF in Trace.Elements)
                        if (XF.TypeName == "cim:PowerTransformer")
                        {
                            CIM_Element ElemToCheck = XF.ParseSingle("etx:TransformerKluge", XF.CIM);
                            if (ElemToCheck == null)
                                ElemToCheck = XF;

                            int VoltageCount = 0;
                            foreach (CIM_Element XFw in ElemToCheck.TransformerWinding)
                                if (this.Voltages.Contains(XFw.VoltageLevel))
                                    VoltageCount++;
                            if (VoltageCount == this.Voltages.Length)
                                FoundVoltage = true;
                        }
                }
                else
                {
                    foreach (CIM_Element Voltage in Voltages)
                        if (this.Voltages.Contains(Voltage))
                            FoundVoltage = true;
                }

                //Now, check our values.
                if (this.Voltages.Length > 0 && !FoundVoltage)
                    return false;
                if (this.PUNs.Length > 0 && !FoundPUN)
                    return false;
                if (this.Substations.Length > 0 && !FoundSubstation)
                    return false;
                if (this.Counties.Length > 0 && !FoundCounty)
                    return false;
                if (this.Counties.Length == 0 && this.Voltages.Length == 0 && this.PUNs.Length == 0 && this.Substations.Length == 0)
                    return FoundAny;
                return true;
            }

            /// <summary>
            /// Compute the data for our trace based on our target element type
            /// </summary>
            /// <param name="Trace"></param>
            /// <param name="ContingencySeeds"></param>
            /// <param name="ElementType"></param>
            /// <param name="Voltages"></param>        
            /// <param name="Substations"></param>
            /// <param name="Counties"></param>
            /// <param name="PUNs"></param>
            /// <param name="FoundAny"></param>
            public void ComputeConnectivity(CIM_Trace Trace, Dictionary<CIM_RdfID, CIM_Element> ContingencySeeds, String ElementType, out List<CIM_Element> Voltages, out List<CIM_Element> Substations, out List<MM_Boundary> Counties, out List<CIM_Element> PUNs, out bool FoundAny)
            {

                Voltages = new List<CIM_Element>();
                Substations = new List<CIM_Element>();
                Counties = new List<MM_Boundary>();
                PUNs = new List<CIM_Element>();
                FoundAny = false;
                foreach (CIM_Element Elem in Trace.Elements)
                    if ((String.IsNullOrEmpty(ElementType) || ElementType == Elem.TypeName) && (ContingencySeeds == null || ContingencySeeds.ContainsKey(Elem.rdfID)))
                    {
                        FoundAny = true;
                        //First, add in our voltage if necessary
                        if (!Voltages.Contains(Elem.VoltageLevel))
                            Voltages.Add(Elem.VoltageLevel);

                        //Add in our substations and counties
                        foreach (CIM_Element Sub in (ElementType == "cim:ACLineSegment" ? Elem.ACSegmentStations : new CIM_Element[] { Elem.Substation }))
                            if (Sub != null)
                            {
                                if (!Substations.Contains(Sub))
                                    Substations.Add(Sub);
                                MM_Substation FoundSub;
                                if (Trace.CIM.Repository.Db.Substations.TryGetValue(Sub.rdfID, out FoundSub) && FoundSub.Boundary != null && !Counties.Contains(FoundSub.Boundary))
                                    Counties.Add(FoundSub.Boundary);
                            }
                        CIM_Element PUN;
                        if (Trace.CIM.PUNElements.TryGetValue(Elem.rdfID, out PUN) && !PUNs.Contains(PUN))
                            PUNs.Add(PUN);
                    }
            }
            #endregion


            #region Identification
            /// <summary>
            /// Report an easy to read string
            /// </summary>
            /// <returns></returns>
            public override string ToString()
            {
                StringBuilder sB = new StringBuilder((IncorporateElements ? "Include " : "Exclude ") + ContingencyCollections.ToString());
                CombineString(Counties, "       Counties:", sB);
                CombineString(Substations, "       Substations:", sB);
                CombineString(Voltages, "       Voltages:", sB);
                CombineString(PUNs, "       PUNs:",sB);
                return sB.ToString();
            }

            /// <summary>
            /// Combine our collection into a string.
            /// </summary>
            /// <param name="Collection"></param>
            /// <param name="Title"></param>
            /// <param name="sB"></param>
            /// <returns></returns>
            private void CombineString(Array Collection, String Title, StringBuilder sB)
            {
                if (Collection.Length == 0)
                    return;
                sB.AppendLine();
                
                for (int a=0; a < Collection.Length; a++)                   
                {
                    Object obj = Collection.GetValue(a);
                    String ObjName = "";
                        if (obj.GetType().GetField("Name") != null)
                            ObjName = obj.GetType().GetField("Name").GetValue(obj).ToString();
                        else if (obj.GetType().GetProperty("Name") != null)
                            ObjName = obj.GetType().GetProperty("Name").GetValue(obj, null).ToString();
                    
                    sB.Append((a == 0 ? Title + " ": ", ") + ObjName);
                }
                
            }
            #endregion

            #region IComparable Members
            /// <summary>
            /// Compare this contingency special group against another.
            /// </summary>
            /// <param name="obj"></param>
            /// <returns></returns>
            public int CompareTo(object obj)
            {
                CIM_Contingency_Grouping.CIM_ContSpecialElements Other = obj as CIM_Contingency_Grouping.CIM_ContSpecialElements;
                if (IncorporateElements != Other.IncorporateElements)
                    return -IncorporateElements.CompareTo(Other.IncorporateElements);
                else if (ContingencyCollections != Other.ContingencyCollections)
                    return ContingencyCollections.CompareTo(Other.ContingencyCollections);
                else
                    return BaseElement.rdfID.CompareTo(Other.BaseElement.rdfID);
            }

            #endregion
        }
        
    }
}